using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.IO;
using System.Collections.Specialized;
using System.Net.Sockets;
using System.Text.RegularExpressions;
using Rest;
using Rest.Service;
using Rest.Transport;
using Coriander.Google.Services.Authentication;  

namespace Coriander.Google.Services
{
    /// <summary>
    /// Google calendar service
    /// </summary>
    /// <remarks>
    /// See: http://code.google.com/apis/gdata/calendar.html
    /// See: Client login: http://code.google.com/apis/accounts/AuthForInstalledApps.html
    /// </remarks>
    public class GoogleCalendar : GoogleServiceProxyBase
    {
        #region  Properties
        /// <summary>
        /// Auth ticket
        /// </summary>
        private GoogleAuthTicket authTicket;

        /// <summary>
        /// The string cl, which is the service name for Google Calendar.
        /// </summary>
        private String serviceName = "cl"; // Has no numbers in it!!!
        private String accountType = "GOOGLE_OR_HOSTED";

        /// <summary>
        /// Identifies your client application. 
        /// Should take the form companyName-applicationName-versionID; 
        /// </summary>
        private String source = "sample-sample.com";
        private String email;
        private String password;
        private String sessionId;

        /// <summary>
        /// Session identifier
        /// </summary>
        public String SessionId
        {
            get { return sessionId; }
            private set { sessionId = value; }
        }

        /// <summary>
        /// True if authentication has succeeded
        /// </summary>
        public Boolean Authenticated
        {
            get { return authTicket != null && authTicket.Auth != null; }
        }

        /// <summary>
        /// Authentication ticket
        /// </summary>
        public GoogleAuthTicket AuthTicket
        {
            get { return authTicket; }
            set { authTicket = value; }
        }
        #endregion

        /// <summary>
        /// ctor : GoogleAuthTicket
        /// </summary><param name="authTicket"></param>
        public GoogleCalendar(GoogleAuthTicket authTicket)
        {
            this.authTicket = authTicket;
        }

        /// <summary>
        /// ctor : String, String
        /// </summary>
        /// <param name="email"></param>
        /// <param name="password"></param>
        public GoogleCalendar(String email, String password)
        {
            this.email = email;
            this.password = password;
            EnsureAuthenticated(); 
        }

        /// <summary>
        /// 
        /// </summary>
        public void EnsureAuthenticated()
        {
            EnsureAuthenticated(false);    
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="force"></param>
        private void EnsureAuthenticated(Boolean force)
        {
            if (force)
            {
                authTicket = null;
            }

            if (!Authenticated)
            {
                ClientLogin clientLogin = new ClientLogin();
                authTicket = clientLogin.Authenticate(
                    accountType, 
                    email,
                    password, 
                    serviceName, 
                    source
                );
            }
        }

        /// <summary>
        /// Formats a calendar date suitable for an atom request
        /// </summary>
        /// <param name="dateTime"></param>
        /// <returns></returns>
        private String FormatAtomDate(DateTime dateTime)
        {
            String formatString = "yyyy-MM-ddTHH:mm:ss.fffZ";
            return dateTime.ToString(
                formatString,
                System.Globalization.CultureInfo.InvariantCulture
            );
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="dateTime"></param>
        /// <returns></returns>
        private String FormatUrlDate(DateTime dateTime)
        {
            String formatString = "yyyy-MM-ddTHH:mm:ss";
            return dateTime.ToString(
                formatString,
                System.Globalization.CultureInfo.InvariantCulture
            );
        }

        /// <summary>
        /// Creates a calendar event
        /// </summary>
        /// <remarks>
        /// See: http://code.google.com/apis/gdata/calendar.html#add_event
        /// 
        /// Posts an Atom XML entry element using a RestDataItem which contains the
        /// raw bytes of the XML.
        /// 
        /// Data is written to the service using Rest.Http.HttpContentType.Atom 
        /// encoding
        /// </remarks>
        /// <param name="title"></param>
        /// <param name="message"></param>
        /// <param name="author"></param>
        /// <param name="location"></param>
        /// <param name="start"></param>
        /// <param name="end"></param>
        /// <returns></returns>
        public XmlDocument CreateEvent(String title, 
            String message, String author, String location, DateTime start, 
            DateTime end)
        {
            EnsureAuthenticated();
 
            RestCommand command = new RestCommand();
            command.ServiceUri  = new Uri("http://www.google.com/calendar/feeds/default/private/full");
            command.Transport   = RestTransport.Post;  
            command.Transport.HttpContentType = Rest.Http.HttpContentType.Atom;
            command.AllowAutoRedirect = true;

            // Redirection policy, ensure redirects keep the same HTTP method
            RestRedirectionPolicy redirectPolicy = new RestRedirectionPolicy();
            redirectPolicy.PreserveHttpTransportOnRedirect = true;
            command.RedirectionPolicy = redirectPolicy;

            command.Headers.Add("Authorization: GoogleLogin auth=" + AuthTicket.Auth);

            String atomXmlText =
                "<entry xmlns=\"http://www.w3.org/2005/Atom\" xmlns:gd=\"http://schemas.google.com/g/2005\">" +
                "  <title type=\"text\">" + title + "</title>" +
                "  <content type='text'>" + message + "</content>" + 
                "  <author>" + 
                "    <name>" + author + "</name>" + 
                "    <email>" + email + "</email>" + 
                "  </author>" +
                "  <where valueString='" + location + "' />" + 
                "  <when startTime='" + FormatAtomDate(start) + "' " +
                "       endTime='" + FormatAtomDate(end) + " '></when>" + 
                "</entry>";
           
            RestDataItem atomXml = new RestDataItem("atom", 
                Encoding.UTF8.GetBytes(atomXmlText));

            atomXml.ContentType = "application/atom+xml";

            command.Data.Add(atomXml);  

            return command.ExecuteXml();
        }

        /// <summary>
        /// Gets all of the user's calendars
        /// </summary>
        /// <remarks>
        /// See: http://code.google.com/apis/gdata/calendar.html#list_calendars
        /// 
        /// [!] Problems selecting nodes
        /// If the XPath expression does not include a prefix, it is assumed that the 
        /// namespace URI is the empty namespace. 
        /// If your XML includes a default namespace, you must still use the 
        /// XmlNamespaceManager and add a prefix and namespace URI to it; 
        /// otherwise, you will not get any nodes selected.
        /// </remarks>
        /// <returns></returns>
        public XmlDocument GetCalendars()
        {
            EnsureAuthenticated();

            RestCommand command = new RestCommand();
            command.ServiceUri = new Uri(
                "http://www.google.com/calendar/feeds/" +
                email);
            command.Transport = new GetRestTransport();
            command.AllowAutoRedirect = true;
            command.Headers.Add("Authorization", "GoogleLogin auth=" + AuthTicket.Auth);
            
            // Save a redirect if we're already within a session
            if (SessionId != null)
            { 
                command.Parameters.Add("gsessionid", SessionId); 
            }

            command.Redirecting += new EventHandler<RestCommandEventArgs>(command_Redirecting);

            return command.ExecuteXml();
        }

        /// <summary>
        /// Handle redirection
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void command_Redirecting(object sender, RestCommandEventArgs e)
        {
            if (e.Response.Headers["Location"] != null)
            {
                Uri redirectUri = new Uri(e.Response.Headers["Location"]);

                if (redirectUri.Query != null)
                {
                    NameValueCollection parameters =
                        System.Web.HttpUtility.ParseQueryString(redirectUri.Query);

                    if (parameters["gsessionid"] != null)
                    {
                        SessionId = parameters["gsessionid"];
                    }
                }
            }
        }

        /// <summary>
        /// Gets the set of items falling between the supplied dates
        /// </summary>
        /// <param name="startMin"></param>
        /// <param name="startMax"></param>
        /// <returns></returns>
        public XmlDocument GetItems(DateTime startMin, DateTime startMax)
        {
            EnsureAuthenticated();

            RestCommand command = new RestCommand();
            command.Redirecting += new EventHandler<RestCommandEventArgs>(command_Redirecting);
            command.ServiceUri = new Uri("http://www.google.com/calendar/feeds/" + 
                "default/private/full");
            command.Transport = RestTransport.Get;
            command.AllowAutoRedirect = true;
            command.Headers.Add("Authorization", "GoogleLogin auth=" + AuthTicket.Auth);

            command.Parameters.Add("start-min", FormatUrlDate(startMin));
            command.Parameters.Add("start-max", FormatUrlDate(startMax)); 

            // Save a redirect if we're already within a session
            if (SessionId != null)
            {
                command.Parameters.Add("gsessionid", SessionId);
            }

            return Invoke(command);
        }

        /// <summary>
        /// Obtains access control list (ACL) for the current calendar
        /// </summary>
        /// <remarks>
        /// See: http://code.google.com/apis/calendar/developers_guide_protocol.html#View_ACL
        /// </remarks>
        /// <returns>
        /// <?xml version=\"1.0\" encoding=\"UTF-8\"?>
        /// <feed 
        ///     xmlns=\"http://www.w3.org/2005/Atom\" 
        ///     xmlns:openSearch=\"http://a9.com/-/spec/opensearchrss/1.0/\" 
        ///     xmlns:gAcl=\"http://schemas.google.com/acl/2007\">
        ///     <id>http://www.google.com/calendar/feeds/ben.biddington%40gmail.com/acl/full</id>
        ///     <updated>2007-05-14T15:20:56.000Z</updated>
        ///     <category 
        ///         scheme=\"http://schemas.google.com/g/2005#kind\" 
        ///         term=\"http://schemas.google.com/acl/2007#accessRule\">
        ///     </category>
        ///     <title type=\"text\">Ben Biddington's access control list</title>
        ///     <link rel=\"http://schemas.google.com/acl/2007#controlledObject\" 
        ///         type=\"application/atom+xml\" 
        ///         href=\"http://www.google.com/calendar/feeds/ben.biddington%40gmail.com/private/full\">
        ///     </link>
        ///     <link rel=\"http://schemas.google.com/g/2005#feed\" 
        ///         type=\"application/atom+xml\" 
        ///         href=\"http://www.google.com/calendar/feeds/ben.biddington%40gmail.com/acl/full\">
        ///     </link>
        ///     <link 
        ///         rel=\"http://schemas.google.com/g/2005#post\" 
        ///         type=\"application/atom+xml\" 
        ///         href=\"http://www.google.com/calendar/feeds/ben.biddington%40gmail.com/acl/full\">
        ///     </link>
        ///     <link 
        ///         rel=\"self\" type=\"application/atom+xml\" 
        ///         href=\"http://www.google.com/calendar/feeds/ben.biddington%40gmail.com/acl/full\">
        ///     </link>
        ///     <generator 
        ///         version=\"1.0\" 
        ///         uri=\"http://www.google.com/calendar\">Google Calendar
        ///     </generator>
        ///     <openSearch:totalResults>2</openSearch:totalResults>
        ///     <openSearch:startIndex>1</openSearch:startIndex>
        ///     <entry>
        ///         <id>http://www.google.com/calendar/feeds/ben.biddington%40gmail.com/acl/full/user%3Aben.biddington%40gmail.com</id>
        ///         <updated>2007-05-14T15:20:56.000Z</updated>
        ///         <category 
        ///             scheme=\"http://schemas.google.com/g/2005#kind\" 
        ///             term=\"http://schemas.google.com/acl/2007#accessRule\">
        ///         </category>
        ///         <title type=\"text\">owner</title>
        ///         <content type=\"text\"></content>
        ///         <link 
        ///             rel=\"self\" 
        ///             type=\"application/atom+xml\" 
        ///             href=\"http://www.google.com/calendar/feeds/ben.biddington%40gmail.com/acl/full/user%3Aben.biddington%40gmail.com\">
        ///         </link>
        ///         <link 
        ///             rel=\"edit\" 
        ///             type=\"application/atom+xml\" 
        ///             href=\"http://www.google.com/calendar/feeds/ben.biddington%40gmail.com/acl/full/user%3Aben.biddington%40gmail.com\">
        ///         </link>
        ///         <author>
        ///             <name>Ben Biddington</name>
        ///             <email>ben.biddington@gmail.com</email>
        ///         </author>
        ///         <gAcl:scope type=\"user\" value=\"ben.biddington@gmail.com\"></gAcl:scope>
        ///         <gAcl:role value=\"http://schemas.google.com/gCal/2005#owner\"></gAcl:role>
        ///     </entry>
        ///     [...]
        /// </feed>
        /// </returns>
        public XmlDocument GetAccessControlList()
        {
            if (null == email)
            {
                throw new InvalidOperationException("Missing e-mail address");
            }

            EnsureAuthenticated();

            String url = String.Format(
                "http://www.google.com/calendar/feeds/{0}/acl/full", this.email);

            RestCommand command = new RestCommand(
                 new Uri(url),
                RestTransport.Get);

            command.AllowAutoRedirect = true;
            command.Headers.Add("Authorization", "GoogleLogin auth=" + AuthTicket.Auth);

            return Invoke(command); 
        }
    }
}
